package org.type4neo;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.type4neo.imp.TypedPropertyContainer;

/**
 * Base interface for all strongly typed node interfaces.
 * 
 * @author pragone
 *
 */
public interface TypedNode extends Node, TypedPropertyContainer {
	/**
	 * Gets the original Neo4J Node that this object wraps
	 * 
	 * @return The original Neo4J Node that this object wraps
	 */
	public Node getUnderlyingNode();
	
	/**
	 * Changes the type associated with this Relationship.
	 * 
	 * @param newType The new type to associate with this Relationship
	 * @return The new strongly typed wrapper around this Relationship that behaves like the <code>newType</code> 
	 */
	public Node mutateTo(Class<? extends TypedNode> newType);
	
	/**
	 * Convenience method to populate a node with data from a compatible object.
	 * 
	 * For example:<br>
	 * Let's suppose this node is of type <code>PersonNode</code>, that's defined as:
	 * <p>
	 * <pre>
	 * <code>
	 * public interface Person {
	 * 	public String getFirstName();
	 * 	public void setFirstName(String firstName);
	 * }
	 * 
	 * public interface PersonNode extends Person, TypedNode {
	 * }
	 * 
	 * </code>
	 * </pre>
	 * Then, any object that implements the <code>Person</code> interface can be used as a parameter to this method.
	 * 
	 * @param obj The object to copy data from
	 * @throws NullPointerException If <code>obj</code> is null
	 * @throws IllegalArgumentException If <code>obj</code> is not type compatible with this TypedNode
	 */
	public void copyFrom(Object obj);
	
	/**
	 * Convenience method to extract the data from a TypedNode into a compatible object.
	 * 
	 * For example:<br>
	 * Let's suppose this node is of type <code>PersonNode</code>, that's defined as:
	 * <p>
	 * <pre>
	 * <code>
	 * public interface Person {
	 * 	public String getFirstName();
	 * 	public void setFirstName(String firstName);
	 * }
	 * 
	 * public interface PersonNode extends Person, TypedNode {
	 * }
	 * 
	 * </code>
	 * </pre>
	 * Then, any object that implements the <code>Person</code> interface can be used as a parameter to this method.
	 * 
	 * @param obj The object to copy data to
	 * @throws NullPointerException If <code>obj</code> is null
	 * @throws IllegalArgumentException If <code>obj</code> is not type compatible with this TypedNode
	 */
	public void copyTo(Object obj);
	
	/**
	 * Untyped version of getRelationships.
	 * 
	 * @deprecated Use {@link #getRelationships(Class...)} instead
	 */
	@Deprecated
	public Iterable<Relationship> getRelationships(RelationshipType... types);
	
	/**
     * Returns all the relationships of any of the types in <code>types</code>
     * that are attached to this node, regardless of direction. If no
     * relationships of the given types are attached to this node, an empty
     * iterable will be returned.
     * 
     * @param types the given relationship type(s)
     * @return all relationships of the given type(s) that are attached to this
     *         node
     */
	public Iterable<TypedRelationship> getRelationships(Class<? extends TypedRelationship>... types);

	/**
	 * Untyped version of getRelationships.
	 * 
	 * @deprecated Use {@link #getRelationships(Class, Direction)} instead
	 */
	@Deprecated
	public Iterable<Relationship> getRelationships(RelationshipType type, Direction dir);
	
	/**
     * Returns all relationships with the given type and direction that are
     * attached to this node. If there are no matching relationships, an empty
     * iterable will be returned.
     * 
     * @param type the given type
     * @param dir the given direction, where <code>Direction.OUTGOING</code>
     *            means all relationships that have this node as
     *            {@link Relationship#getStartNode() start node} and <code>
	 * Direction.INCOMING</code>
     *            means all relationships that have this node as
     *            {@link Relationship#getEndNode() end node}
     * @return all relationships attached to this node that match the given type
     *         and direction
     */
	public Iterable<TypedRelationship> getRelationships(Class<? extends TypedRelationship> type, Direction dir);

	/**
	 * Untyped version of hasRelationship.
	 * 
	 * @deprecated Use {@link #hasRelationship(Class, Direction)} instead
	 */
	@Deprecated
	public boolean hasRelationship(RelationshipType type, Direction dir);
	
	/**
     * Returns <code>true</code> if there are any relationships of the given
     * class and direction attached to this node, <code>false</code>
     * otherwise.
     * 
     * @param type the given type
     * @param dir the given direction, where <code>Direction.OUTGOING</code>
     *            means all relationships that have this node as
     *            {@link Relationship#getStartNode() start node} and <code>
	 * Direction.INCOMING</code>
     *            means all relationships that have this node as
     *            {@link Relationship#getEndNode() end node}
     * @return <code>true</code> if there are any relationships of the given
     *         relationship type and direction attached to this node,
     *         <code>false</code> otherwise
     */
	public boolean hasRelationship(Class<? extends TypedRelationship> type, Direction dir);
	
	/**
	 * Untyped version of getSingleRelationship.
	 * 
	 * @deprecated Use {@link #getSingleRelationship(Class, Direction)} instead
	 */
	@Deprecated
	public Relationship getSingleRelationship(RelationshipType type, Direction dir);
	
	/**
     * Returns the only relationship of a given type and direction that is
     * attached to this node, or <code>null</code>. This is a convenience method
     * that is used in the commonly occurring situation where a node has exactly
     * zero or one relationships of a given type and direction to another node.
     * Typically this invariant is maintained by the rest of the code: if at any
     * time more than one such relationships exist, it is a fatal error that
     * should generate an unchecked exception. This method reflects that
     * semantics and returns either:
     * <p>
     * <ol>
     * <li><code>null</code> if there are zero relationships of the given type
     * and direction,</li>
     * <li>the relationship if there's exactly one, or
     * <li>throws an unchecked exception in all other cases.</li>
     * </ol>
     * <p>
     * This method should be used only in situations with an invariant as
     * described above. In those situations, a "state-checking" method (e.g.
     * <code>hasSingleRelationship(...)</code>) is not required, because this
     * method behaves correctly "out of the box."
     * 
     * @param type the type of the wanted relationship
     * @param dir the direction of the wanted relationship (where
     *            <code>Direction.OUTGOING</code> means a relationship that has
     *            this node as {@link Relationship#getStartNode() start node}
     *            and <code>
	 * Direction.INCOMING</code> means a relationship that has
     *            this node as {@link Relationship#getEndNode() end node}) or
     *            {@link Direction#BOTH} if direction is irrelevant
     * @return the single relationship matching the given type and direction if
     *         exactly one such relationship exists, or <code>null</code> if
     *         exactly zero such relationships exists
     * @throws RuntimeException if more than one relationship matches the given
     *             type and direction
     */
	public TypedRelationship getSingleRelationship(Class<? extends TypedRelationship> type, Direction dir);
	
	/**
	 * Untyped version of createRelationshipTo.
	 * 
	 * @deprecated Use {@link #createRelationshipTo(Node, Class)} instead
	 */
	@Deprecated
	public Relationship createRelationshipTo(Node otherNode, RelationshipType type);
	
	/**
     * Creates a relationship between this node and another node. The
     * relationship is of type <code>type</code>. It starts at this node and
     * ends at <code>otherNode</code>.
     * <p>
     * Neo4j doesn't support self-relationships, i.e. relationships where the
     * start node and end node are the same. If <code>otherNode</code> is the
     * same as this node then an exception will be thrown.
     * <p>
     * A relationship is equally well traversed in both directions so there's no
     * need to create another relationship in the opposite direction (in regards
     * to traversal or performance).
     * 
     * @param otherNode the end node of the new relationship
     * @param type the type of the new relationship
     * @return the newly created relationship
     */
	public TypedRelationship createRelationshipTo(Node otherNode, Class<? extends TypedRelationship> type);
}
